/**
 * 
 */
package neptune.manager.builder;

import java.util.ArrayList;
import java.util.BitSet;

import neptune.manager.NeptuneManager;
import neptune.manager.model.experiment.Experiment;
import neptune.manager.model.topology.IPAddress;
import neptune.manager.storage.ExperimentRepositoryException;
import neptune.manager.utils.BitUtils;
import neptune.manager.utils.IllegalValueException;
import neptune.manager.utils.UnsignedByte;

/**
 * Gestisce la creazione di un indirizzo di subnet univoco per un esperimento.
 * E' importante notare che la gestione delle subnet di questo builder prevede
 * che i bit 1 della subnet mask siano tutti contigui.
 * 
 * @author Roberto Bifulco [RobertoBifulco.it] [info@robertoBifulco.it]
 * 
 */
public class ExperimentSubnetBuilder {

	private IPAddress baseSubnet;
	private IPAddress clusterSubnet;
	private int experimentPrefixLenght;
	private NeptuneManager neptuneManager;

	/**
	 * I parametri passati vengono adoperati per il processo di generazione di
	 * una subnet univoca. La base subnet rappresenta l'indirizzo della subnet
	 * di base adoperata dal sistema per gli esperimenti. Il riferimento al
	 * NeptuneManager e' necessario per garantire che la subnet generata non sia
	 * gia' in uso.
	 * 
	 * @param baseSubnet
	 * @param experimentPrefixLenght
	 * @param neptuneManager
	 */
	public ExperimentSubnetBuilder(IPAddress baseSubnet,
			IPAddress clusterSubnet, NeptuneManager neptuneManager) {
		super();
		this.baseSubnet = baseSubnet;
		this.clusterSubnet = clusterSubnet;
		this.neptuneManager = neptuneManager;

		try {
			UnsignedByte[] mask = IPAddress.toUnsignedBytes(baseSubnet
					.getSubnetMask());

			BitSet maskBits = BitUtils.getBitValue(mask);
			experimentPrefixLenght = maskBits.cardinality();

		} catch (IllegalValueException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Genera un indirizzo di subnet univoco per un esperimento
	 * 
	 * @return
	 * @throws BuilderException
	 */
	public IPAddress generateExperimentSubnet() throws BuilderException {
		try {
			ArrayList<IPAddress> usedSubnets = new ArrayList<IPAddress>(0);

			for (Experiment exp : neptuneManager.getExperiments()) {
				if (exp.getAssignedSubnet() != null)
					usedSubnets.add(exp.getAssignedSubnet());
			}

			IPAddress generated = baseSubnet;
			while (usedSubnets.contains(generated)) {
				generated = getNextSubnet(generated);
				if (generated == null)
					throw new BuilderException(
							"Unable to create experiment subnet");
				else if (!clusterSubnet.include(generated))
					throw new BuilderException(
							"Unable to create experiment subnet");
			}

			return generated;
		} catch (ExperimentRepositoryException e) {
			throw new BuilderException(e);
		}
	}

	/**
	 * Restituisce il successivo indirizzo di subnet in base al valore di
	 * subnetmask specificato alla costruzione di questo oggetto. Se non esiste
	 * un successivo indirizzo, restituisce null.
	 * 
	 * @param actualSubnet
	 * @return
	 */
	public IPAddress getNextSubnet(IPAddress actualSubnet) {
		try {
			BitSet addr = BitUtils.getBitValue(actualSubnet.getUnsignedBytes());

			if (BitUtils.incrementBits(addr, experimentPrefixLenght, 32))
				return null; // l'incremento ha causato overflow

			String addrString = IPAddress.toAddressString(IPAddress
					.getAddressBytesFromBits(addr));

			return new IPAddress(4, addrString, actualSubnet.getSubnetMask());
		} catch (IllegalValueException e) {
			throw new RuntimeException(e);
		}
	}
}
